Importing functionality from Java

Java interfaces and classes can be imported from Java by declaring them inside importJava block:

importJava "java.util.regex.Pattern" where
    data Pattern

importJava "java.util.List" where
    data List a

Java methods, constructors and fields can be similarly imported by giving their type annotations in importJava block:

importJava "java.util.regex.Pattern.compile" where
    @JavaName compile
    compilePattern :: String -> Pattern

    @JavaName matcher
    createMatcher :: Pattern -> String -> <Proc> Matcher

importJava "java.util.regex.Matcher" where
    data Matcher

    @JavaName matches
    matcherMatches :: Matcher -> <Proc> Boolean

matches :: Pattern -> String -> <Proc> Boolean
matches pattern text = do
    matcherMatches (createMatcher pattern text)

Another example:

importJava "java.util.ArrayList" where
    @JavaName "<init>"
    createArrayList :: () -> <Proc> List a

    @JavaName "<init>"
    createArrayListWithCapacity :: Integer -> <Proc> List a

    @JavaName size
    sizeList :: List a -> <Proc> Integer

    @JavaName get
    getList :: List a -> Integer -> <Proc> a

    @JavaName set
    setList :: List a -> Integer -> a -> <Proc> ()

    @JavaName add
    addList :: List a -> a -> <Proc> Boolean

Java constructor is referred with "<init>". If Java method name and SCL name matches the annotation @JavaName can be left out. Java import mechanism tries to be quite flexible. It provides some arguments based on the effects the function has. It also ignores the return value of the Java method if the return type is () in SCL.

Static methods are matched by their SCL type signature alone — no extra argument for the class. Instance methods receive the class instance as their first argument in the SCL type signature.

A major functionality currently still missing is the ability to create new implementations of existing Java interfaces in SCL code or extend an existing class. This can be worked around currently by writing new implementations in Java.

Defining type classes

Type classes define a set of operations that a type must support. Declare a class with the class keyword, and provide instances with instance:

class Container f where
    empty  :: f a
    insert :: a -> f a -> f a
    toList :: f a -> [a]

instance Container [] where
    empty  = []
    insert = (:)
    toList = id

Type class constraints in signatures use the ClassName typeVar => syntax:

printAll :: (Container f, Show a) => f a -> <Proc> ()
printAll c = iter (print . show) (toList c)

Classes form a hierarchy: a class may declare superclasses that any instance must also satisfy:

class Eq a => Ord a where
    compare :: a -> a -> Ordering

Defining effects

New effect types are declared with the effect keyword:

effect MyEffect

Functions that use this effect declare it in their return type:

doSomething :: Integer -> <MyEffect> ()

Effects are used to track which side-channel capabilities a function uses, enabling safe composition and static verification that effects are handled at the correct layer.

Binary operator precedence

Custom binary operators can be given explicit precedence and associativity using infix, infixl, or infixr declarations:

infixl 6 +, -
infixl 7 *, /
infixr 5 :
infix  4 ==, /=, <, <=, >, >=
  • infixl — left-associative (most arithmetic operators)
  • infixr — right-associative (:: type annotation, $, ., list cons :)
  • infix — non-associative (comparison operators; chaining is a type error)

The numeric argument is the precedence level (higher binds tighter). Standard levels: application has effectively infinite precedence, $ has 0, || has 2, && has 3, comparisons have 4, +/- have 6, *// have 7.

Restricted imports

An import statement can expose only a subset of a module's exports:

import "Prelude" (map, filter, foldl)

Or hide specific names while importing everything else:

import "Prelude" hiding (sort)

This is useful for avoiding name clashes when two modules export the same identifier.

Documentation strings

A triple-quoted string literal placed immediately before a definition serves as its documentation comment:

"""
Computes the square root of the sum of squares of the components.
"""
magnitude :: (Double, Double, Double) -> Double
magnitude (x, y, z) = sqrt (x*x + y*y + z*z)

Documentation strings are available to tooling (hover text in the editor, generated docs).

Private definitions

Definitions can be marked private to prevent them from being exported from the module:

private
    helperFunction :: Integer -> Integer
    helperFunction n = n * 2 + 1

Private definitions are invisible to importers; they can only be used within the same module.

Relational sublanguage

SCL includes a relational sublanguage for declarative data queries and constraint enforcement, built on the same infrastructure as the Simantics graph database rules.

Key keywords:

  • select — query expression; returns a collection of results matching a pattern.
  • when — conditional trigger in a rule body.
  • enforce — asserts a constraint that must hold.
  • rule / transformation — declare inference rules and model transformations that run automatically when the graph changes.

This sublanguage is primarily used for ontology-level programming and is beyond the scope of this tutorial. Refer to the Simantics platform documentation for details.